package com.culture.bootdemo.common.aop;


import com.alibaba.fastjson.JSON;
import com.culture.bootdemo.utils.Md5Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.TimeUnit;


@Component
@Slf4j
@Aspect
public class RedisCacheableInterceptor {
    /**
     * 注解RedisCacheable 的实现方法，针对dao接口层的注解
     */


    @Resource
    private RedisTemplate<String, String> redisTemplate;
    private static final String CACHE_KEY_PREFIX = "CACHE_KEY_PREFIX_%s";


    @Around(value = "@annotation(com.culture.bootdemo.common.aop.RedisCacheable)")
    public Object executeAround(ProceedingJoinPoint point) throws Throwable {
        try {
            /*if(!ddCloudStringRedisClient.isRedisEnable()){
                //如果redis未开启，直接执行
                return point.proceed();
            }*/
            Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
            RedisCacheable ann = method.getAnnotation(RedisCacheable.class);
            if (ann == null) {
                return point.proceed();
            }
            //get and set from cache
            int cacheTime = ann.cacheTime();
            // get args
            Object[] args = point.getArgs();
            String methodName = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
            String key = String.format(CACHE_KEY_PREFIX, Md5Utils.getMD5(ann.key() + methodName + JSON.toJSONString(args)));
            Class<?> returnType = method.getReturnType();
            if (returnType.isAssignableFrom(List.class)) { //判断是否为List
                Type gt = method.getGenericReturnType();
                if (gt instanceof ParameterizedType) {
                    //ParameterizedType 参数化类型
                    Type[] actualTypeArguments = ((ParameterizedType) gt).getActualTypeArguments();
                    //获取到的<T>中的T的真实的类型
                    Type actualType = actualTypeArguments[0];
                    try {
                        returnType = Class.forName(actualType.getTypeName());
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                return handleList(point, returnType, cacheTime, args, key);
            } else {
                return handleSingle(point, returnType, cacheTime, args, key);
            }
        } catch (Exception e) {
            log.error("RedisCacheableInterceptor executeAround error", e);
            return point.proceed();
        }
    }

    @SuppressWarnings("unchecked")
    private <T> List<T> handleList(ProceedingJoinPoint point,
                                   Class<T> abstractTypeReference, int cacheTime,
                                   Object[] args, String key) throws Throwable {

        if (StringUtils.isBlank(key)) {
            return null;
        }
        List<T> list = null;
        //redis cluster can set retry times, remove all exception handling
        try {
            String valueRedis = redisTemplate.opsForValue().get(key);
            list = JSON.parseArray(valueRedis, abstractTypeReference);
        } catch (Exception e) {
            log.error("RedisCacheableInterceptor unknown redis exception:", e);
        }
        if (list == null) {
            list = (List<T>) point.proceed();
            if (CollectionUtils.isNotEmpty(list)) {
                redisTemplate.opsForValue().set(key, JSON.toJSONString(list), cacheTime, TimeUnit.SECONDS);
                log.info("RedisCacheableInterceptor got data in database to cache, key={}", key);
            } else {
                redisTemplate.delete(key);
            }

        } else {
            log.info("RedisCacheableInterceptor got data in redis, key={}", key);
        }
        return list;
    }

    @SuppressWarnings("unchecked")
    private <T> T handleSingle(ProceedingJoinPoint point,
                               Class<T> abstractTypeReference, int cacheTime,
                               Object[] args, String key) throws Throwable {

        if (StringUtils.isBlank(key)) {
            return null;
        }
        T result = null;
        try {
            String valueRedis = redisTemplate.opsForValue().get(key);
            result = JSON.parseObject(valueRedis, abstractTypeReference);

        } catch (Exception e) {
            log.warn("RedisCacheableInterceptor unknown redis exception, key={} ", key, e);
        }
        if (result == null) {
            result = (T) point.proceed();
            if (result != null) {
                redisTemplate.opsForValue().set(key, JSON.toJSONString(result), cacheTime, TimeUnit.SECONDS);
                log.info("RedisCacheableInterceptor got data in database to cache, key={}", key);
            } else {
                redisTemplate.delete(key);
            }
        } else {
            log.info("RedisCacheableInterceptor got data in redis, key={}", key);
        }
        return result;
    }

}
